/*
 *  Board.m: Board of Gomoku.app
 *
 *  Copyright (c) 2000 Nicola Pero <n.pero@mi.flashnet.it> (most of the code)
 *  Copyright (c) 2004 Adam Nohejl <adam.nohejl@kppm.cz> (parts marked (Loki))
 *  
 *  Author: Nicola Pero
 *  Date: May 2000
 *
 *  Author: David Relson 
 *  Date: September 2000, modified for boards of arbitrary size
 *
 *  Author: Adam Nohejl (parts marked (Loki))
 *  Date: 2004-02-21 - 2004-04-08, added 6th level algorithm and more
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "Board.h"

/* Changes and additions by Adam Nohejl 2004-02-21 - 2004-04-08 marked with "(Loki)"
* Copyright (c) 2004 Adam Nohejl <adam.nohejl@kppm.cz>
*/

#include <time.h>


/* (Loki){ */
enum{
	sndClick	= 1,
	sndWon		= 2,
	sndLost		= 3
};

enum{
	numSoundsLost	= 2,
	numSoundsWon	= 2
};

/* }(Loki)*/



static const tile humanWonPattern[5] = 
{
	human, human, human, human, human
};

static const tile computerWonPattern[5] = 
{ 
	computer, computer, computer, computer, computer 
};

/*
 * I swear I have beaten (at least once) this engine at all levels.  
 * So, don't be afraid of trying yet another time - it *is* possible 
 * to win, no matter how high the level is. :-)
 */

static const tile humanWonNextPattern[5][5] = 
{ 
	{nothing, human, human, human, human},
	{human, nothing, human, human, human},
	{human, human, nothing, human, human},
	{human, human, human, nothing, human},
	{human, human, human, human, nothing}
};

static const tile computerWonNextPattern[5][5] = 
{ 
	{nothing, computer, computer, computer, computer},
	{computer, nothing, computer, computer, computer},
	{computer, computer, nothing, computer, computer},
	{computer, computer, computer, nothing, computer},
	{computer, computer, computer, computer, nothing}
};

static const tile humanWonFarPattern[4][6] = 
{
	{nothing, nothing, human, human, human, nothing},
	{nothing, human, nothing, human, human, nothing},
	{nothing, human, human, nothing, human, nothing},
	{nothing, human, human, human, nothing, nothing}
};

static const tile computerWonFarPattern[4][6] = 
{
	{nothing, nothing, computer, computer, computer, nothing},
	{nothing, computer, nothing, computer, computer, nothing},
	{nothing, computer, computer, nothing, computer, nothing},
	{nothing, computer, computer, computer, nothing, nothing}
};


#define	BOARD(i, j) board[((i) * size + (j))]

void chk(int i, int j, int size);
void chk(int i, int j, int size){
	if(i>=size)
		fprintf(stderr,"i=%i>size ",i);
	if(j>=size)
		fprintf(stderr,"j=%i>size ",j);
	fflush(stdout);
}


#define	INDEX(i, j) ((i) * size + (j))

@implementation Board

+ (id) newWithRows: (int)cnt
{
    return [[self alloc] initWithRows: cnt];
}

- (id) initWithRows: (int)cnt
{
    size = cnt;
    board = malloc (sizeof(tile *) * size * size);
	
    [self initBoardConstants];
    
    return self;
}

- (void) initBoardConstants
{
    /*int startRow, startColumn, row, column, position, value;*/
    int startRow, startColumn, position;
	
    boardConstants = calloc (size * size, sizeof (int));
	
    /* The board constant of position (i, j) is the number of winning
		patterns which pass through (i, j) */
	
    /* We compute it in the most stupid way, by enumerating all
		winning combinations and for each winning combination adding
		one to each board cell belonging to the combination.  
		This is stupid and long, but the code is easy to understand and 
		maintain; nowadays computers are fast enough. */
    
    /* Scan horizontal combinations */
    for (startRow = 0; startRow < size; startRow++)
	{
		for (startColumn = 0; startColumn < (size + 1 - 5); startColumn++)
		{
			for (position = 0; position < 5; position++)
			{
				boardConstants[INDEX (startRow, startColumn + position)]++; 
			}
		}
	}
	
    /* Scan vertical combinations */
    for (startColumn = 0; startColumn < size; startColumn++)
	{
		for (startRow = 0; startRow < (size + 1 - 5); startRow++)
		{
			for (position = 0; position < 5; position++)
			{
				boardConstants[INDEX (startRow + position, startColumn)]++; 
			}
		}
	}
	
    /* Scan NW-SE diagonals. */
    for (startColumn = 0; startColumn < (size + 1 - 5); startColumn++)
	{
		for (startRow = 0; startRow < (size + 1 - 5); startRow++)
		{
			for (position = 0; position < 5; position++)
			{
				boardConstants[INDEX (startRow + position, 
									  startColumn + position)]++;
			}
		}
	}
	
    /* Scan NE-SW diagonals. */
    for (startColumn = (5 - 1); startColumn < size; startColumn++)
	{
		for (startRow = 0; startRow < (size + 1 - 5); startRow++)
		{
			for (position = 0; position < 5; position++)
			{
				boardConstants[INDEX (startRow + position, 
									  startColumn - position)]++;
			}
		}
	}
}

- (oneway void) dealloc
{
    free (board);
    free (boardConstants);
}

// Initialize, reset, set level
- (void) reset
{
	int i, j/*, v*/;
	
	for (i = 0; i < size; i++)
		for (j = 0; j < size; j++)
		{
			BOARD (i, j) = nothing;
		}
			
			last_move_row = -1;
	last_move_col = -1;
}

- (id) copyWithZone: (NSZone*)zone
{
	int i, j;
	Board	*c = [isa allocWithZone: zone];
	
	c->difficultyLevel = difficultyLevel;
	
	c->board = malloc (sizeof(tile *) * size * size);
	c->boardConstants = malloc (sizeof(int) * size * size);
	
	for (i = 0; i < size; i++)
		for (j = 0; j< size; j++)
		{
			c->BOARD (i, j) = BOARD (i, j);
			c->boardConstants[INDEX (i, j)] = boardConstants[INDEX (i, j)];
		}
			
			c->last_move_row = last_move_row;  
	c->last_move_col = last_move_col;
	
	return c;
}

- (void) setDifficultyLevel: (int) i
{
	difficultyLevel = i;
}

- (int) difficultyLevel
{
	return difficultyLevel;
}

- (tile) tileInRow: (int) i  column: (int) j
{
	return BOARD (i, j);
}

- (void) setTile: (tile) t inRow: (int) i column: (int) j
{
	BOARD (i, j) = t;
}

- (BOOL) isFreeRow: (int) i  column: (int) j
{
	if (BOARD (i, j) == nothing)
    {
		return YES;
    }
	else 
    {
		return NO;  
    }
	
}

- (BOOL) isPattern: (const tile *)t
			length: (int)e
		inPosition: (patternPosition)pos
{
	int a;
	int i, j;
	
	i = pos.startRow;
	j = pos.startColumn;
	
	for (a = 0; a < e; a++)
    {
		if (t[a] COMPARE_TILE BOARD (i, j))
		{
			i += pos.directionRow;
			j += pos.directionColumn;
		}
		else
		{
			return NO;
		}
    }
	
	return YES;
}

- (patternPosition) scanBoardForPattern: (const tile *)t
								 length: (int)e
{
	// Extremely simple and lossy algorithm and implementation: we
	// consider all possible pattern positions on the board (easy to
	// enumerate), and for each position we compare the pattern to what
	// it is on the board (easy to do).
	patternPosition pos;
	
	// Scan rows.
	pos.directionRow = 0;
	pos.directionColumn = 1;
	for (pos.startRow = 0; pos.startRow < size; pos.startRow++)
    {
		for (pos.startColumn = 0; pos.startColumn < (size + 1 - e); pos.startColumn++)
		{
			if ([self isPattern: t length: e inPosition: pos] == YES)
				return pos;
		}
    }
	
	// Scan columns.
	pos.directionRow = 1;
	pos.directionColumn = 0;
	for (pos.startColumn = 0; pos.startColumn < size; pos.startColumn++)
    {
		for (pos.startRow = 0; pos.startRow < (size + 1 - e); pos.startRow++)
		{
			if ([self isPattern: t length: e inPosition: pos] == YES)
				return pos;
		}
    }
	
	// Scan NW-SE diagonals.
	pos.directionRow = 1;
	pos.directionColumn = 1;
	for (pos.startColumn = 0; pos.startColumn < (size + 1 - e); pos.startColumn++)
    {
		for (pos.startRow = 0; pos.startRow < (size + 1 - e); pos.startRow++)
		{
			if ([self isPattern: t length: e inPosition: pos] == YES)
				return pos;
		}
    }
	
	// Scan NE-SW diagonals.
	pos.directionRow = 1;
	pos.directionColumn = -1;
	for (pos.startColumn = (e - 1); pos.startColumn < size; pos.startColumn++)
    {
		for (pos.startRow = 0; pos.startRow < (size + 1 - e); pos.startRow++)
		{
			if ([self isPattern: t length: e inPosition: pos] == YES)
				return pos;
		}
    }
	
	// Not Found
	pos.startRow = -1;
	return pos;
}

/* 
* Computes a number representing the strategical importance of the
 * square in row, column.  This is all pretty unprecise and simple,
 * but that's nice - it looks more human. :-) */
- (float) powerOfSquareInRow: (int)row column: (int)column
{
	/* Temporary board to make computations */
	Board *tmp_board;
	patternPosition pos;
	int index;
	float squarePower = 0;
	
	if (difficultyLevel >= 4)
    {
		tmp_board = [self copy];
		
		/* Put a computer tile in (row, column) */
		tmp_board->BOARD (row, column) = computer;
		
		/* Check how many next computer wins we would have with that move */
		for (index = 0; index < 5; index++)
		{
			pos = [tmp_board scanBoardForPattern: computerWonNextPattern[index]
										  length: 5];
			if (PATTERN_FOUND (pos))  
			{
				squarePower += 10;
			}
		}
		
		/* Put a human tile in (row, column) */
		tmp_board->BOARD (row, column) = human;
		
		/* Check how many next human wins we would have with that move */
		for (index = 0; index < 5; index++)
		{
			pos = [tmp_board scanBoardForPattern: humanWonNextPattern[index]
										  length: 5];
			if (PATTERN_FOUND (pos))  
			{
				squarePower += 10;
			}
		}
		
		/* If difficultyLevel => 5, do the same with FarPatterns. */
		if (difficultyLevel >= 5)
		{
			/* Put a computer tile in (row, column) */
			tmp_board->BOARD (row, column) = computer;
			
			/* Check how many far computer wins we would have with that move */
			for (index = 0; index < 4; index++)
			{
				pos = [tmp_board scanBoardForPattern: 
					computerWonFarPattern[index]
											  length: 6];
				if (PATTERN_FOUND (pos))  
				{
					squarePower += 5;
				}
			}
			
			/* Put a human tile in (row, column) */
			tmp_board->BOARD (row, column) = human;
			
			/* Check how many far human wins we would have with that move */
			for (index = 0; index < 4; index++)
			{
				pos = [tmp_board scanBoardForPattern: humanWonFarPattern[index]
											  length: 6];
				if (PATTERN_FOUND (pos))  
				{
					squarePower += 5;
				}
			}
		}
		
		RELEASE (tmp_board);
    }
	
	squarePower += boardConstants[INDEX (row, column)];
	
	return squarePower;
}
/*
 * All the following return YES upon succesfully finding a nice move
 * to do; NO otherwise.  */
/* This is always succesful.  Play in a square with lots of tiles
around (at level 0 - much more refined at higher levels) */

- (BOOL) computerMoveInCrowdedPlace{
	/* Temporary board to make computations */
	int *tmp_calculate = malloc (sizeof(int) * size * size );
	int max = 0;
	float importance_of_max = 0;
	int i_max = 0;
	int j_max = 0;
	int i,j;
	float tmp_importance;
	
	/* TODO this ensures that the first turn is played near the center, but I should fix the
		* same thing for next moves ...  (Loki)*/
	if(last_move_col==-1){
		last_move_row = size/2;
		last_move_col = last_move_row;
		/* Fixed a bug that the computer will dominate the center forever no matter who takes first turn (Li Wei Nan) */
		if (BOARD(last_move_row, last_move_col) != human) {
			BOARD(last_move_row, last_move_col) = computer;
		} else {
			BOARD(last_move_row, last_move_col+1) = computer;
		}
		return YES;
	}
	
	
	/* reset tmp_calculate */
	for (i = 0; i < size; i++)
    {
		for(j = 0; j < size; j++)
		{
			tmp_calculate[INDEX(i, j)] = 0;
		}    
    }
	
	/* Look for crowded places */
	for (i = 1; i < size - 1; i++)
    {
		for (j = 1; j < size - 1; j++)
		{
			if (BOARD(i, j) COMPARE_TILE (human | computer))
			{
				tmp_calculate[INDEX (i+1, j-1)]++;
				tmp_calculate[INDEX (i+1, j  )]++;
				tmp_calculate[INDEX (i+1, j+1)]++;
				tmp_calculate[INDEX (i,   j-1)]++;
				tmp_calculate[INDEX (i,   j+1)]++;
				tmp_calculate[INDEX (i-1, j-1)]++;
				tmp_calculate[INDEX (i-1, j  )]++;
				tmp_calculate[INDEX (i-1, j+1)]++;
			}
		}
    }
	
	/* avoid squares already occupied */
	for (i = 0; i < size; i++)
    {
		for(j = 0; j < size; j++)
		{	  
			if (BOARD(i, j) COMPARE_TILE (human | computer))
			{
				tmp_calculate[INDEX (i, j)]=0;
			}
			else
			{
				/* we store here a non-busy cell in case we don't find
				anything better */
				i_max=i;
				j_max=j;
			}
		}    
    }
	
	/* now we extract the solution */
	for (i = 0; i < size; i++)
    {
		for(j = 0; j < size; j++)
		{
			if (difficultyLevel >= 3)
			{
				if (tmp_calculate[INDEX (i, j)] >= max)
				{
					tmp_importance = [self powerOfSquareInRow: i column: j];
					if ((tmp_calculate[INDEX (i, j)] > max) 
						|| (tmp_importance > importance_of_max))
					{
						importance_of_max = tmp_importance;
						max = tmp_calculate[INDEX (i, j)];
						i_max=i;
						j_max=j;
					}
				}
			}
			else
			{
				/* same without being too smart - just look for the most crowded
				board square */
				if (tmp_calculate[INDEX (i, j)] > max)
				{
					max = tmp_calculate[INDEX (i, j)];
					i_max = i;
					j_max = j;
				}
			}
		}
    }
	
	BOARD(i_max, j_max) = computer;
	last_move_row = i_max;
	last_move_col = j_max;
	
	free (tmp_calculate);
	
	return YES;
}

/* another algorithm (Loki) */

/* This algorithm is based on the idea, that every empty square can be ranked (from the view
* of one of the players) with a certain amount of points correspondig to the profit of making
* player's next move in it.
*
* We first find empty squares preferably not far from existing pieces of the player. Then we find
* the one of them with the highest rank. We do this both for us (the computer) and for the human.
*
* Then we choose the square with the highest rank of both players' squares. If highest ranks are
* equal then we choose the one of ours.
*
* Square gets points for every line of five squares without other player's pieces which contains it.
* The number of points it gets is based on the number of our pieces in the line. It's 2 even if there's
* no piece of ours (empty line is still beter than no line), with growing number of stones it increses.
* One exception is when there's a line with four pieces of ours, then the square doesn't get any
* additional points for the other lines. (All lines with four pieces of ours and one empty square
										  * ensure our victory, there is no differnce between squares with such lines.)
*
* The algorithm may be improved by ranking each square only once by (our profit)-(other player's
																				  * profit) and smarter profit-based ranking. But the current algorithm is goog enough and still gives
* the human a chance to win.
*
* As opposed to the original Pero's algorithm it doesn't use patterns and is more than half shorter
* and little bit faster. Although it can be considered less human-like.
*
*/

enum{
	rank0 = 2,
	rank1 = 3,
	rank2 = 32,
	rank3 = 513, /* = rank2*16+1 */
	rank4 = 8204 /* = rank3*16+1 -> ensures max */
};

- (BOOL) computerMoveWithLokiAlgorithm{
	const int	directionsX[]	= {0, 1, 1, 1};
	const int	directionsY[]	= {1, 1, 0,-1};
	/* directions of lines containing the square:
		*    1,-1
		*    /
		*   *--1,0
		*   |\
		* 0,1 1,1	 */
	int			who, me, you;
	const int	ranks[]	= {rank0,rank1,rank2,rank3,rank4};	/* our pieces -> rank */
	int*		thinkBoard = calloc(size*size,sizeof(int));
	int			i, j, x, xm, y, ym, dx, dy, d, k, l;
	int			squaresAround = 0;
	int			foundSquareWithPieceNear = 0;
	int			numMyPieces;
	int			rank, cBestRank=0, hBestRank=0, cBestRankX, cBestRankY, hBestRankX, hBestRankY;
	int*		bestRank; int* bestRankX; int* bestRankY;
	tile		thisTile;
	
	/* TODO this ensures that the first turn is played near the center, but I should fix the
		* same thing for next moves ...  (Loki)*/
	if(last_move_col==-1){
		last_move_row = size/2;
		last_move_col = last_move_row;
		/* Fixed a bug that the computer will dominate the center forever no matter who takes first turn (Li Wei Nan) */
		if (BOARD(last_move_row, last_move_col) != human) {
			BOARD(last_move_row, last_move_col) = computer;
		} else {
			++last_move_row;
			BOARD(last_move_row, last_move_col) = computer;
		}
		return YES;
	}
	
	
	/* find empty squares near existing pieces */
	for(i=0;i<size;i++){
		for(j=0;j<size;j++){
			if(BOARD(i,j)==nothing){
				x				= i>2		? i-2 : 0;
				xm				= i<size-2	? i+2 : size-1;
				y				= j>2		? j-2 : 0;
				ym				= j<size-2	? j+2 : size-1;
				squaresAround   = 0;
				for(;x<=xm;x++){
					for(;y<=ym;y++)
						squaresAround |= BOARD(x,y); 
				}
				if(squaresAround & computer){
					foundSquareWithPieceNear &= computer;
					thinkBoard[INDEX(i,j)] |= computer;
				}
				if(squaresAround & human){
					foundSquareWithPieceNear &= human;
					thinkBoard[INDEX(i,j)] |= human;
				}
				/* if algorithm finds nothing remember at least an empty square */
				cBestRankX = i;
				cBestRankY = j;
			}
		}
	}
	
	if(!(foundSquareWithPieceNear & (computer|human))){
		/* find empty squares */
		foundSquareWithPieceNear ^= (computer|human);
		for(i=0;i<size;i++){
			for(j=0;j<size;j++){
				if(BOARD(i,j)==nothing)
					thinkBoard[INDEX(i,j)] |= foundSquareWithPieceNear;
			}
		}
	}
	/* rank the squares, we have found */
	for(who=0;who<2;who++){
		if(who){
			/* second loop-pass */
			me	= human;
			you	= computer;
			bestRank	= &hBestRank;
			bestRankX	= &hBestRankX;
			bestRankY	= &hBestRankY;
		}else{
			/* first loop-pass */
			me	= computer;
			you	= human;
			bestRank	= &cBestRank;
			bestRankX	= &cBestRankX;
			bestRankY	= &cBestRankY;
		}
		for(i=0;i<size;i++){
			for(j=0;j<size;j++){
				if(!thinkBoard[INDEX(i,j)])
					continue;
				rank = 0;
				/* 4 directions */
				for(d=0;d<4;d++){
					dx = directionsX[d];
					dy = directionsY[d];
					/* 4 displacements */
					for(k=-4;k<=0;k++){
						/* check if we aren't out of board */
						if(i+dx*k<0 || j+dy*k<0 || i+dx*(k+4)>=size || j+dy*(k+4)>=size ||
						   j+dy*k>=size || j+dy*(k+4)<0)	/* This is possible too, added in 1.4.3 */
						   continue;
						   numMyPieces = 0;
						   /* line of 5 squares */
						   for(l=0;l<5;l++){
							   thisTile = BOARD(i+dx*(k+l),j+dy*(k+l));
							   if(thisTile & you)
								   break;
							   if(thisTile & me)
								   numMyPieces++;
						   }
						   if(l!=5)
						   continue; /* for broken == oponent in this line (5 squares) */
						   if(numMyPieces==4){
							   *bestRank	= rank4;
							   *bestRankX	= i;
							   *bestRankY	= j;
							   goto stopSearch;
							   /* I don't like GOTO, but I think this is one of the rare cases it's
								   * OK to use it. */
						   }
						   rank += ranks[numMyPieces];
					}
				}
if(rank > /*>=*/ *bestRank){
	*bestRank	= rank;
	*bestRankX	= i;
	*bestRankY	= j;
}
			}
		}
	}
stopSearch:
if(hBestRank > cBestRank){
	/* human has better move than we have, so we take it before he does */
	//NSLog(@"hBestRank > cBestRank (%i>%i) @ %i,%i",hBestRank,cBestRank,hBestRankX,hBestRankY);
	BOARD(hBestRankX, hBestRankY) = computer;
	last_move_row = hBestRankX;
	last_move_col = hBestRankY;
}else{
	//NSLog(@"cBestRank >= hBestRank (%i>%i) @ %i,%i",cBestRank,hBestRank,cBestRankX,cBestRankY);
	BOARD(cBestRankX, cBestRankY) = computer;
	last_move_row = cBestRankX;
	last_move_col = cBestRankY;
}
free(thinkBoard);
return YES;
}


- (BOOL) computerOrHumanWinNext 
{
	patternPosition pos;
	int index;
	
	for (index = 0; index < 5; index++)
    {
		pos = [self scanBoardForPattern: computerWonNextPattern[index]
								 length: 5];
		if (PATTERN_FOUND (pos))
		{
			int a, i, j;
			
			i = pos.startRow;
			j = pos.startColumn;
			for (a = 0; a < index; a++)
			{
				i += pos.directionRow;
				j += pos.directionColumn;
			}
			BOARD(i, j) = computer;
			last_move_row = i;
			last_move_col = j;
			return YES;
		}
    }
	
	for (index = 0; index < 5; index++)
    {
		pos = [self scanBoardForPattern: humanWonNextPattern[index]
								 length: 5];
		if (PATTERN_FOUND (pos))
		{
			int a, i, j;
			
			i = pos.startRow;
			j = pos.startColumn;
			for (a = 0; a < index; a++)
			{
				i += pos.directionRow;
				j += pos.directionColumn;
			}
			BOARD(i, j) = computer;
			last_move_row = i;
			last_move_col = j;
			return YES;
		}
    }
	return NO;
}

- (BOOL) computerOrHumanWinFar
{
	patternPosition pos;
	int index;
	
	for (index = 0; index < 4; index++)
    {
		pos = [self scanBoardForPattern: computerWonFarPattern[index]
								 length: 6];
		if (PATTERN_FOUND (pos))
		{
			int a, i, j;
			
			i = pos.startRow;
			j = pos.startColumn;
			for (a = 0; a < (index + 1); a++)
			{
				i += pos.directionRow;
				j += pos.directionColumn;
			}
			BOARD(i, j) = computer;
			last_move_row = i;
			last_move_col = j;
			return YES;
		}
    }
	
	for (index = 0; index < 4; index++)
    {
		pos = [self scanBoardForPattern: humanWonFarPattern[index]
								 length: 6];
		if (PATTERN_FOUND (pos))
		{
			int a, i, j;
			
			i = pos.startRow;
			j = pos.startColumn;
			for (a = 0; a < (index + 1); a++)
			{
				i += pos.directionRow;
				j += pos.directionColumn;
			}
			BOARD(i, j) = computer;
			last_move_row = i;
			last_move_col = j;
			return YES;
		}
    }
	return NO;
}

- (void) performComputerMove{
	
	switch (difficultyLevel){
		case 6:
			/* (Loki) */
			[self computerMoveWithLokiAlgorithm];
			break;
		case 5:
		case 4:
		case 3:
			if ([self computerOrHumanWinNext] == YES)
				break;
			if ([self computerOrHumanWinFar] == YES)
				break;
				/* All the refinement for higher levels goes here */
				[self computerMoveInCrowdedPlace];
			break;    
		case 2:
			if ([self computerOrHumanWinNext] == YES)
				break;
			if ([self computerOrHumanWinFar] == YES)
				break;
				[self computerMoveInCrowdedPlace];
			break;    
		case 1:
			if ([self computerOrHumanWinNext] == YES)
				break;
			/* else fall back on level 0 strategy */
		case 0:
		default:
			[self computerMoveInCrowdedPlace];
			break;
	}
	return;
}
/* Last row and column computer moved to */
- (int) lastRow
{
	return last_move_row;
}

- (int) lastColumn
{
	return last_move_col;
}	

@end

@implementation MainBoard

@synthesize board;

- (id) initWithMatrix: (NSMatrix *)m
{
	/*self = [super init]; HUH? (Loki) */
	ASSIGN (matrix, m);
	size = [m numberOfRows];
	board = [Board newWithRows: size ];
	[self newGame];
	return self;
}

/* (Loki) : */
- (NSMatrix *) refreshMatrix{
	int i, j;
	
	//NSLog(@"refreshMatrix * * *");
	
	for (i = 0; i < size; i++) {
		for (j = 0; j < size; j++)
			[self setTile: [board tileInRow: i column: j] inRow: i column: j];
    }
	[matrix sizeToFit];
	//NSLog(@"* * *");
	return matrix;
}


- (void) dealloc
{
	RELEASE (matrix);
	RELEASE (board);
	[super dealloc];
}

- (void) newGame{
	int i, j;
	
	//NSLog(@"newGame * * * *");
	/* Reset board */
	[board reset];
	
	if(zoom){	/* if (Loki) [else wait for refreshMatrix]*/
		for (i = 0; i < size; i++){
			for (j = 0; j < size; j++){
				[self setTile: nothing inRow: i column: j];
			}
		}
	}
	turnsPlayed = 0;
	firstTurnComputer = NO;	/* this is the default value, can be changed by calling performCompuetMove.. */
	/* Answer to user input */
	isGameActive = YES;
	//NSLog(@"* * * *");
}

- (BOOL) isFreeRow: (int) i  column: (int) j
{
	if ([board tileInRow: i column: j] == nothing)
		return YES;
	else
		return NO;
}

- (void) userMoveInRow: (int) i  column: (int) j setTile: (tile) myTile
{
	//NSLog(@"tile: %d", (int) myTile);
	patternPosition pos;
	
	if (isGameActive == NO)
		return;
	
	if ([board tileInRow: i column: j] != nothing)
		return;
	
	[board setTile: myTile inRow: i column: j];
	[self setTile: myTile inRow: i column: j];
	[self playSound:sndClick]; /* (Loki) */
	
	if (myTile==human) {
		pos = [board scanBoardForPattern: humanWonPattern length: 5];
	} else if (myTile==computer) {
		pos = [board scanBoardForPattern: computerWonPattern length: 5];
	}
	
	if (PATTERN_FOUND (pos))
    {
		// Human won
		// Someone won - Li Wei Nan
		int a, i, j;
		
		isGameActive = NO;
		i = pos.startRow;
		j = pos.startColumn;
		for (a = 0; a < 5; a++)
		{
			[self setWinningTile: human  inRow: i  column: j];
			i += pos.directionRow;
			j += pos.directionColumn;
		}
		[self playSound:sndWon]; /* (Loki) */
		[self panel: _(@"Game Over")  info: _(@"Great! You win!")];
		return;
    }
	
	//NB: Human plays first, and the number of available squares is
	//even, so that if we are here, there still is a void square.
	//if (myType == humanToComputer) {
	//	[board performComputerMove];
	
	//	[self setTile: computer  inRow: [board lastRow] 
	//		   column: [board lastColumn]];
	//	[self playSound:sndClick];  /* (Loki) */
	//	pos = [board scanBoardForPattern: computerWonPattern length: 5];
	//	if (PATTERN_FOUND (pos))
	//	{
	// Computer won
	//		int a, i, j;
	
	//		isGameActive = NO;
	//		i = pos.startRow;
	//		j = pos.startColumn;
	//		for (a = 0; a < 5; a++)
	//		{
	//			[self setWinningTile: computer  inRow: i  column: j];
	//			i += pos.directionRow;
	//			j += pos.directionColumn;
	//		}
	//		[self playSound:sndLost]; /* (Loki) */
	//		[self panel: _(@"Game Over")  info: _(@"Sorry, you lose.")];
	//	}  
	//}
	// If the spaces are full, end the game.
	turnsPlayed++;
	if (turnsPlayed >= ((size * size) / 2))
{
		isGameActive = NO;
		[self panel: _(@"Game Over")  info: _(@"Quits")];
}  
}

- (void) setDifficultyLevel: (int) i
{
	/* (Loki) TODO 6 */
	if ((i >=0) && (i <= 6))
    {
		difficultyLevel = i;
		[board setDifficultyLevel: i];
    }
	else
    {
		NSLog (_(@"Internal Error: unknown difficultyLevel"));
    }
}

- (int) difficultyLevel
{
	return difficultyLevel;
}

/* 
* Methods used to display the board. 
 * Override/rewrite the following methods to port the game 
 * to another GUI framework.
 */
// Change contents of (row i, column j) to display tile t (which can
// be nothing, computer or human, see enum above)
- (void) setTile: (tile) t
		   inRow: (int) i  
		  column: (int) j
{
	NSImageCell *cell;
	NSRect rect;
	
	/* (Loki) themes (imageOfKind:...) */
	cell = [matrix cellAtRow: i column: j];
	[cell setImage: [self imageOfKind:t forPositionX:i y:j]];
	rect = [matrix cellFrameAtRow: i column: j];
	[matrix setNeedsDisplayInRect: rect];
}

// Change contents of (row i, column j) to display a winning tile of
// type t (which can be nothing, computer or human, see enum above).
// This is invoked when one the two players win, to highlight the
// winning combination.
- (void) setWinningTile: (tile) t
				  inRow: (int) i
				 column: (int) j
{
	// TODO
}

// Display an alert to the user through a pop up panel; typically
// invoked as: [self panel: @"Game Over" info: @"Human won"];
- (void) panel: (NSString *)s
		  info: (NSString *)t
{
	NSRunAlertPanel (s, t, _(@"OK"), NULL, NULL);
}

/* (Loki){ */

- (void) playSound:(int)soundID{
	static BOOL	srandCalled = NO;
	
	if(!sounds)
		return;
	
	if(!srandCalled){
		srandom(time(NULL));
		srandCalled = YES;
	}
	if(soundID == sndClick){
		[[NSSound soundNamed:@"click"] play];
	}else if(soundID == sndWon)
		[[NSSound soundNamed:
			[NSString stringWithFormat:@"won%02i",random()%numSoundsWon]
			] play];
	else if(soundID == sndLost)
		[[NSSound soundNamed:
			[NSString stringWithFormat:@"lost%02i",random()%numSoundsLost]
			] play];
}

- (NSImage*) imageOfKind:(int)kind forPositionX:(int)x y:(int)y{
	NSString*	imageName = nil;
	NSString*	kindName;
	NSImage*	image;
	
	/* black if player who has started the game */
	kindName = kind==nothing  ?  @"Empty"  :  (kind==computer?firstTurnComputer:!firstTurnComputer) ? @"Black" : @"White";
	
	if(theme == thmClassic){
		if(x == 0)
			imageName = @"Top";
		else if(x == size-1)
			imageName = @"Bottom";
		else
			imageName = @"";
		if(y == 0)
			imageName = [imageName stringByAppendingString: @"Left"];
		else if(y == size-1)
			imageName = [imageName stringByAppendingString: @"Right"];
		imageName = [kindName stringByAppendingString:imageName];
	}else if(theme == thmOX)
		imageName = [@"ThemeOX" stringByAppendingString:kindName];
	
	image = [NSImage imageNamed: imageName];
	[image setScalesWhenResized:YES];
	[image setSize:NSMakeSize(zoom,zoom)];
	//if(!x && !y)
	//	NSLog(@"name: %@  img: %p  setScales:1  setSize: %i",imageName,image,zoom);
	return image;
}

- (void)	setSounds:(BOOL)s theme:(int)t zoom:(int)z{
	sounds	= s;
	theme	= t;
	zoom	= z;
}
/* this is called when computer starts the game */
/* this is called everytime computer moves - Li Wei Nan */
- (void)	performComputerMove{
	patternPosition pos;
	if(!turnsPlayed)
		firstTurnComputer = YES;
	[board performComputerMove];
	[self setTile: computer  inRow: [board lastRow]
		   column: [board lastColumn]];
	[self playSound:sndClick];  /* (Loki) */
	pos = [board scanBoardForPattern: computerWonPattern length: 5];
	if (PATTERN_FOUND (pos))
	{
		// Computer won
		int a, i, j;
		
		isGameActive = NO;
		i = pos.startRow;
		j = pos.startColumn;
		for (a = 0; a < 5; a++)
		{
			[self setWinningTile: computer  inRow: i  column: j];
			i += pos.directionRow;
			j += pos.directionColumn;
		}
		[self playSound:sndLost]; /* (Loki) */
		[self panel: _(@"Game Over")  info: _(@"Sorry, you lose.")];
	}  
	turnsPlayed++;
}

- (int)		humanTurnsPlayed{
	return turnsPlayed/2;
	/* there's virtually no delay before the computer plays -> total/2 should be right */
}


/* }(Loki) */

@end




